/*
 * AudioRecorder.java
 *
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.zxh.audio;

/**
 *
 * @author
 */
import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;

public class AudioRecorder implements IAudioRecorder, Runnable {
    /**
     * AudioInputStream used for sending the cached recorded audio to an IAudioPlayer for playback
     */
    protected AudioInputStream audioInputStream = null;

    /**
     * The line used to read in audio data while recording
     */
    protected TargetDataLine line;

    /**
     * Status flag indicating recording state
     */
    protected boolean recording = false;

    /**
     * The AudioFormat used for recording
     */
    protected AudioFormat format = this.DEFAULT_AUDIO_FORMAT;

    /**
     * AudioPlayer used to playback recorded audio
     */
    protected AudioPlayer player = new AudioPlayer();
    


    /**
     * Separate thread to manage recording. This is done since the read method in
     * <code>TargetDataLine</code> blocks. If this separate thread was not used,
     * the calling code would also block while recording.
     */
    protected Thread thread;

    /**
     * DEfault constructor
     */
    public AudioRecorder() 
    {
        setDefaultAudioFormat();
    }


    public void startRecording() throws Exception 
    {
        this.recording = true;
        try 
        {
            player.unloadAudio(); //reset the audio player
        } 
        catch (Exception e)
        {
            //noop
        }

        //check thread state and initialize a new thread to record.
        if (this.thread == null) 
        {
            this.thread = new Thread(this);
            this.thread.start();
        }
    }

    /**
     * The run method used for recording
     */
    public void run() 
    {
        try 
        {

            this.audioInputStream = null;

            //create a new DataLine.Info object with the TargetDataLine class
            //and the current format
            DataLine.Info info
                    = new DataLine.Info(TargetDataLine.class, this.format);

            //request a line from the AudioSystem, passing in the DataLine.Info
            this.line = (TargetDataLine) AudioSystem.getLine(info);
            //open the line with the format and our buffer size
            this.line.open(this.format, this.line.getBufferSize());

            //create a new output stream. When we read from the Line, well write the
            //data to this output stream
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            //calculate the length of the buffer to use for recording
            int frameSizeInBytes = this.format.getFrameSize();
            int bufferLengthInFrames = this.line.getBufferSize() / 8;
            int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
            //create the buffer
            byte[] data = new byte[bufferLengthInBytes];
            //start the line...
            this.line.start();

            int numBytesRead;

            while(recording) 
            {
                //read the data into the byte[]
                if ((numBytesRead = this.line.read(data, 0, bufferLengthInBytes)) == -1) 
                {
                    //if there is an error, stop recording
                    break;
                }
                //write the data out to the byte array and do it all again
                else 
                {
                    out.write(data, 0, numBytesRead);
                }
            }

            // Recording complete. Stop and close the line.
            this.line.stop();
            this.line.close();
            this.line = null;

            // Now, stop and close the output stream
            try 
            {
                out.flush();
                out.close();
            } 
            catch (IOException ex) 
            {
                ex.printStackTrace();
            }

            // Now well get the recorded audio data ready for playback...

            //get the byte[] from the output stream
            byte audioBytes[] = out.toByteArray();
            //create an input stream
            ByteArrayInputStream bais = new ByteArrayInputStream(audioBytes);
            //request an AudioInputStream from the AudioSystem
            this.audioInputStream = new AudioInputStream(bais, this.format, audioBytes.length / frameSizeInBytes);
            //reset the AudioInputStream, sets the current frame postion to the beginning
            this.audioInputStream.reset();

            //load the audio into the AudioPlayer            
            this.player.loadAudio(this.audioInputStream);
        }
        catch (LineUnavailableException lue)
        {
            //this is sort of a kluge. I had trouble retrieving a line to record while
            //a line was open for playback.
            this.recording = false;
            this.thread = null;
            lue.printStackTrace();
        }
        catch (Exception ex) 
        {
            this.thread = null;
            this.recording = false;
            ex.printStackTrace();
        }
    }

    /**
     * @return the AudioPlayer used for playback of the recorded audio
     */
    public AudioPlayer getAudioPlayer() 
    {
        return this.player;
    }
    

    /**
     * Stops recording
     */
    public void stopRecording() 
    {
        //the record flag is checked in each iteration of the record loop.
        //by setting this flag to false, the record loop will exit at the next iteration
        //this safely stops the recording thread
        this.recording = false;
        this.thread = null;
    }

    /**
     * Stops the player from playing
     */
    public void stopPlaying() 
    {
        this.player.stop();
    }

    /**
     * Stops recording or playback. This method is used by the outside world since
     * you don't know if the AudioRecorder is recording or playing back audio
     */
    public void stopEverything() 
    {
        //check the Recording flag and call the appropriate stop method
        if (this.recording == true) 
        {
            this.stopRecording();
        } 
        else 
        {
            this.stopPlaying();
        }
    }

    /**
     * Dumps the recorded audio data
     */
    public void reset() 
    {
        this.audioInputStream = null;
        this.player.unloadAudio();
    }

    /**
     * Saves the recorded audio data to a file
     *
     * @param file The file to save to
     * @param fileType The file type to save as
     * @return returns the status of the save operation
     * @throws Exception
     */
    public boolean saveToFile(File file, AudioFileFormat.Type fileType) throws Exception 
    {
        // reset to the beginnning of the captured data
        this.audioInputStream.reset();
        return (AudioSystem.write(audioInputStream, fileType, file) == -1);
    }

    /**
     * Sets the default audio format
     *
     */
    public void setDefaultAudioFormat() 
    {
        this.format = this.DEFAULT_AUDIO_FORMAT;
    }

    /**
     * @return the current AudioFormat
     */
    public AudioFormat getAudioFormat()
    {
        return this.format;
    }

    /**
     * Sets the audio format
     * @param format The AudioFormat to use
     */
    public void setAudioFormat(AudioFormat format)
    {
        this.format = format;
    }

    /**
     * @return the recording flag. whether or not the AudioRecorder is currently recording
     */
    public boolean isRecording() 
    {
        return recording;
    }

}